/******************************************************************************

	COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2008, 2009, 2010.

	This file is part of the Maxsi Library.

	Maxsi Library is free software: you can redistribute it and/or modify it
	under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or (at your
	option) any later version.

	Maxsi Library is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
	License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with Maxsi Library. If not, see <http://www.gnu.org/licenses/>.

	Maxsi Library
	A powerful Cross-Platform C++ General Purpose Library that allows you to
	efficiently create high-performance and reliable applications.

	MaxsiNetworking.cpp
	Blocking IO in a flexible manner.

******************************************************************************/

#include "MaxsiLibrary.h"

#ifdef Maxsi_Support_POSIX
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include <signal.h>
#endif

#ifdef Maxsi_Support_Epoll
#include <sys/epoll.h>
#endif

BeginMaxsiNamespace

#ifdef Maxsi_Support_POSIX
//=============================================================================
//	MaxsiTCPPOSIX.
//=============================================================================
MAXSI_DEFINE_IMPLEMENTATION(MaxsiTCPPOSIX, ITCP, MAXSI_INTERFACE_STATIC);

//=============================================================================
//	MaxsiTCPPOSIX - Deals with ITCP on POSIX.
//=============================================================================
IBufferStreamer* MaxsiTCPPOSIX::Connect(MESTR* HostName, short Port, IBufferStreamer* Proxy)
{
	ITCPStreamer*	Connection			=	(ITCPStreamer*)CreateInterface("ITCPStreamer");

	if ( Connection == NULL ) { return NULL; }

	// If a proxy was requested, use that instead.
	if ( Proxy != NULL ) { MaxsiError Result = Connection->SetProxyInformation(Proxy, HostName, Port); if ( Result != MAXSI_ERROR_SUCCESS ) { SetError(Result); return NULL; } return Connection; }

	char*			HostNameSL			=	NULL;
	int				Socket				=	-1;
	hostent*		ServerAddressCopy	=	NULL;
	sockaddr_in		ServerAddress;
	bool			Success				=	true;
	
	if ( Success )
	{
		// Convert the Host Name to the system locale.
		HostNameSL	=	MESTR2CHAR(HostName);

		// Check for error conditions.
		if ( HostNameSL == NULL ) { Success = false; }
	}

	if ( Success )
	{
		// Create a socket.
		Socket		=	socket(AF_INET, SOCK_STREAM, 0);

		// Check for error conditions.
		if ( Socket == -1 ) { Success = false; }
	}

#ifdef Maxsi_Support_NoSigPipe
	if ( Success )
	{
		int set = 1;
		Success = (setsockopt(Socket, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int)) == 0);
	}
#endif

	if ( Success )
	{
		// Do the DNS Lookup.
		ServerAddressCopy	=	gethostbyname(HostNameSL);
		MAXSI_TODO("The Open Group doesn't require gethostbyname to be thread safe!");

		// Check for error conditions.
		if ( ServerAddressCopy == NULL || ServerAddressCopy->h_addr == NULL ) { Success = false; }
	}

	if ( Success )
	{	
		// Reset the Server Adress structure.
		memset(&ServerAddress, 0, sizeof(ServerAddress));

		// Set the address type.
		ServerAddress.sin_family = AF_INET;

		// Copy the server address.
		memcpy(&ServerAddress.sin_addr.s_addr, ServerAddressCopy->h_addr, ServerAddressCopy->h_length);

		// Convert the remote port to network order (endianness).
		ServerAddress.sin_port = htons(Port);

		// Connect to the remote host and check for error conditions.
		if ( TEMP_FAILURE_RETRY(connect(Socket, (sockaddr*)&ServerAddress, sizeof(ServerAddress))) != 0 ) { Success = false; }
	}

	if ( Success )
	{
		Connection->SetConnectionHandle((MaxsiHandle)&Socket);
	}

	IFDEL(HostNameSL);

	if ( Success == false ) { if ( Socket != -1 ) { close(Socket); } }
	if ( Success == false ) { DeleteInterface(Connection); return NULL; }

	return Connection;
}

void MaxsiTCPPOSIX::CloseConnection(IBufferStreamer* Connection)
{
	MaxsiHandle SocketHandle = ((ITCPStreamer*)Connection)->GetConnectionHandle();
	
	close(*(int*)SocketHandle);
	delete (int*)SocketHandle;

	DeleteInterface(Connection);
}

//=============================================================================
//	MaxsiTCPStreamerPOSIX.
//=============================================================================
MAXSI_DEFINE_IMPLEMENTATION(MaxsiTCPStreamerPOSIX, ITCPStreamer, MAXSI_INTERFACE_DYNAMIC);

MaxsiTCPStreamerPOSIX::MaxsiTCPStreamerPOSIX()
{
	Socket = -1;
}

MaxsiTCPStreamerPOSIX::~MaxsiTCPStreamerPOSIX()
{
	
}

void MaxsiTCPStreamerPOSIX::SetConnectionHandle(MaxsiHandle Handle)
{
	Socket = *((int*)Handle);
}

MaxsiHandle MaxsiTCPStreamerPOSIX::GetConnectionHandle()
{
	return (MaxsiHandle)&Socket;
}

int MaxsiTCPStreamerPOSIX::GetFlags()
{
	if ( Proxy ) { return Proxy->GetFlags(); }
	return ( MAXSI_BUFFER_STREAMER_FLAG_READABLE | MAXSI_BUFFER_STREAMER_FLAG_WRITABLE );
}

FileSize_t MaxsiTCPStreamerPOSIX::GetSize()
{
	if ( Proxy ) { return Proxy->GetSize(); }
	return FILESIZE_MAX;
}

MaxsiError MaxsiTCPStreamerPOSIX::SetSize(FileSize_t NewSize)
{
	if ( Proxy ) { return Proxy->SetSize(NewSize); }
	return MAXSI_ERROR_NOT_SUPPORTED;
}

FileSize_t MaxsiTCPStreamerPOSIX::GetPosition()
{
	if ( Proxy ) { return Proxy->GetPosition(); }
	return FILESIZE_MAX;	
}

MaxsiError MaxsiTCPStreamerPOSIX::Seek(FileSize_t Offset)
{
	if ( Proxy ) { return Proxy->Seek(Offset); }
	return MAXSI_ERROR_NOT_SUPPORTED;
}

MaxsiError MaxsiTCPStreamerPOSIX::Read(BYTE* Destination, size_t DestinationSize, size_t* ActualRead)
{
	if ( Proxy ) { return Proxy->Read(Destination, DestinationSize, ActualRead); }
	ssize_t Read	=	TEMP_FAILURE_RETRY(recv(Socket, Destination, DestinationSize, MSG_NOSIGNAL));

	if ( Read == -1 || Read > SSIZE_MAX ) { return MAXSI_ERROR_CONNECTION_ISSUE; }

	if ( Read < 0 ) { Read = 0; } MAXSI_TODO("Test if this is correct behavior!");

	*ActualRead = (size_t)(Read);

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiTCPStreamerPOSIX::ReadFrom(FileSize_t Offset, BYTE* Destination, size_t DestinationSize, size_t* ActualRead)
{
	if ( Proxy ) { return Proxy->ReadFrom(Offset, Destination, DestinationSize, ActualRead); }
	return MAXSI_ERROR_NOT_SUPPORTED;
}

MaxsiError MaxsiTCPStreamerPOSIX::Write(BYTE* Source, size_t SourceSize)
{
	if ( Proxy ) { return Proxy->Write(Source, SourceSize); }
	size_t		Left	=	SourceSize;

	while ( Left )
	{
		ssize_t		Written		=	TEMP_FAILURE_RETRY(send(Socket, Source + (SourceSize-Left), SourceSize - Left, MSG_NOSIGNAL));

		if ( Written == -1 ) { return MAXSI_ERROR_CONNECTION_ISSUE; }

		if ( Written < 0 ) { Written = 0; } MAXSI_TODO("Test if this is correct behavior!");

		Left	-=	(size_t)Written;
	}

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiTCPStreamerPOSIX::WriteTo(FileSize_t Offset, BYTE* Source, size_t SourceSize)
{
	if ( Proxy ) { return Proxy->WriteTo(Offset, Source, SourceSize); }
	return MAXSI_ERROR_NOT_SUPPORTED;	
}

MaxsiError MaxsiTCPStreamerPOSIX::WriteFlush()
{
	if ( Proxy ) { return Proxy->WriteFlush(); }
	// We aren't using this, since this class doesn't do any buffering.
	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiTCPStreamerPOSIX::SetProxyInformation(IBufferStreamer* NewProxy, MESTR* HostName, short Port)
{
	// Set a proxy.
	Proxy = NewProxy;
	return MAXSI_ERROR_SUCCESS;
}

#endif

#ifdef Maxsi_Support_W32
//=============================================================================
//	MaxsiTCPW32.
//=============================================================================
MAXSI_DEFINE_IMPLEMENTATION(MaxsiTCPW32, ITCP, MAXSI_INTERFACE_STATIC);

//=============================================================================
//	MaxsiTCPW32 - Deals with ITCP on W32.
//=============================================================================
IBufferStreamer* MaxsiTCPW32::Connect(MESTR* HostName, short Port, IBufferStreamer* Proxy)
{
	ITCPStreamer*	Connection		=	(ITCPStreamer*)CreateInterface("ITCPStreamer");

	// If a proxy was requested, use that instead.
	if ( Proxy != NULL ) { MaxsiError Result = Connection->SetProxyInformation(Proxy, HostName, Port); if ( Result != MAXSI_ERROR_SUCCESS ) { SetError(Result; return NULL; } return Connection; }

	if ( Connection == NULL ) { return NULL; }

	char*			HostNameSL			=	NULL;
	SOCKET			Socket				=	INVALID_SOCKET;
	hostent*		ServerAddressCopy	=	NULL;
	sockaddr_in		ServerAddress;
	bool			Success		=	true;
	
	if ( Success )
	{
		// Convert the Host Name to the system locale.
		HostNameSL	=	MESTR2CHAR(HostName);

		// Check for error conditions.
		if ( HostNameSL == NULL ) { Success = false; }
	}

	if ( Success )
	{
		// Create a socket.
		Socket		=	socket(AF_INET, SOCK_STREAM, 0);

		// Check for error conditions.
		if ( Socket == INVALID_SOCKET ) { Success = false; }
	}

	if ( Success )
	{
		// Do the DNS Lookup.
		ServerAddressCopy	=	gethostbyname(HostNameSL);
		MAXSI_TODO("The Open Group doesn't require gethostbyname to be thread safe!");

		// Check for error conditions.
		if ( ServerAddressCopy == NULL || ServerAddressCopy->h_addr == NULL ) { Success = false; }
	}

	if ( Success )
	{	
		// Reset the Server Adress structure.
		memset(&ServerAddress, 0, sizeof(ServerAddress));

		// Set the address type.
		ServerAddress.sin_family = AF_INET;

		// Copy the server address.
		memcpy(&ServerAddress.sin_addr.s_addr, ServerAddressCopy->h_addr, ServerAddressCopy->h_length);

		// Convert the remote port to network order (endianness).
		ServerAddress.sin_port = htons(Port);

		// Connect to the remote host and check for error conditions.
		if ( connect(Socket, (sockaddr*)&ServerAddress, sizeof(ServerAddress)) != 0 ) { Success = false; }
	}

	if ( Success )
	{
		Connection->SetConnectionHandle((MaxsiHandle)&Socket);
	}

	IFDEL(HostNameSL);

	if ( Success == false ) { if ( Socket != -1 ) { close(Socket); } }
	if ( Success == false ) { DeleteInterface(Connection); return NULL; }

	return Connection;
}

void MaxsiTCPW32::CloseConnection(IBufferStreamer* Connection)
{
	MaxsiHandle SocketHandle = ((ITCPStreamer*)Connection)->GetConnectionHandle();
	
	close(*(SOCKET*)SocketHandle);
	delete (SOCKET*)SocketHandle;

	DeleteInterface(Connection);
}

//=============================================================================
//	MaxsiTCPStreamerW32.
//=============================================================================
MAXSI_DEFINE_IMPLEMENTATION(MaxsiTCPStreamerW32, ITCPStreamer, MAXSI_INTERFACE_DYNAMIC);

MaxsiTCPStreamerW32::MaxsiTCPStreamerW32()
{
	Socket = INVALID_SOCKET_VALUE;
}

MaxsiTCPStreamerW32::~MaxsiTCPStreamerW32()
{
	
}

void MaxsiTCPStreamerW32::SetConnectionHandle(MaxsiHandle Handle)
{
	Socket = *((SOCKET*)Handle);
}

MaxsiHandle MaxsiTCPStreamerW32::GetConnectionHandle()
{
	return (MaxsiHandle)&Socket;
}

int MaxsiTCPStreamerW32::GetFlags()
{
	if ( Proxy ) { return Proxy->GetFlags(); }
	return ( MAXSI_BUFFER_STREAMER_FLAG_READABLE | MAXSI_BUFFER_STREAMER_FLAG_WRITABLE );
}

FileSize_t MaxsiTCPStreamerW32::GetSize()
{
	if ( Proxy ) { return Proxy->GetSize(); }
	return FILESIZE_MAX;
}

MaxsiError MaxsiTCPStreamerW32::SetSize(FileSize_t NewSize)
{
	if ( Proxy ) { return Proxy->SetSize(NewSize); }
	return MAXSI_ERROR_NOT_SUPPORTED;
}

FileSize_t MaxsiTCPStreamerW32::GetPosition()
{
	if ( Proxy ) { return Proxy->GetPosition(); }
	return FILESIZE_MAX;	
}

MaxsiError MaxsiTCPStreamerW32::Seek(FileSize_t Offset)
{
	if ( Proxy ) { return Proxy->Seek(Offset); }
	return MAXSI_ERROR_NOT_SUPPORTED;
}

MaxsiError MaxsiTCPStreamerW32::Read(BYTE* Destination, size_t DestinationSize, size_t* ActualRead)
{
	if ( Proxy ) { return Proxy->Read(Destination, DestinationSize, ActualRead); }
	int Read	=	recv(Socket, (char*)Destination, DestinationSize, 0);

	if ( Read == SOCKET_ERROR || Read == 0 ) { return MAXSI_ERROR_CONNECTION_ISSUE; }

	if ( Read < 0 ) { Read = 0; } MAXSI_TODO("Test if this is correct behavior!");

	*ActualRead = (size_t)(Read);

	return MAXSI_ERROR_SUCCESS;

}

MaxsiError MaxsiTCPStreamerW32::ReadFrom(FileSize_t Offset, BYTE* Destination, size_t DestinationSize, size_t* ActualRead)
{
	if ( Proxy ) { return Proxy->ReadFrom(Offset, Destination, DestinationSize, ActualRead); }
	return MAXSI_ERROR_NOT_SUPPORTED;
}

MaxsiError MaxsiTCPStreamerW32::Write(BYTE* Source, size_t SourceSize)
{
	if ( Proxy ) { return Proxy->Write(Source, SourceSize); }
	size_t		Left	=	SourceSize;

	while ( Left )
	{
		int		Written		=	TEMP_FAILURE_RETRY(send(Socket, Source + (SourceSize-Left), SourceSize - Left, MSG_NOSIGNAL));

		if ( Written == SOCKET_ERROR ) { return MAXSI_ERROR_CONNECTION_ISSUE; }

		if ( Written < 0 ) { Written = 0; } MAXSI_TODO("Test if this is correct behavior!");

		Left	-=	(size_t)Written;
	}

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiTCPStreamerW32::WriteTo(FileSize_t Offset, BYTE* Source, size_t SourceSize)
{
	if ( Proxy ) { return Proxy->WriteTo(Offset, Source, SourceSize); }
	return MAXSI_ERROR_NOT_SUPPORTED;	
}

MaxsiError MaxsiTCPStreamerW32::WriteFlush()
{
	if ( Proxy ) { Proxy->WriteFlush(); }
	// We aren't using this, since this class doesn't do any buffering.
	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiTCPStreamerW32::SetProxyInformation(IBufferStreamer* NewProxy, MESTR* HostName, short Port)
{
	// Set a proxy.
	Proxy = NewProxy;
	return MAXSI_ERROR_SUCCESS;
}

#endif

//=============================================================================
// Various useful functions.
//=============================================================================

IBufferStreamer* CreateTCPConnection(MESTR* HostName, short Port)
{
	ITCP* TCP = (ITCP*) GetStaticClass("ICP");

	return (TCP) ? TCP->Connect(HostName, Port) : NULL;
}

void CloseTCPConnection(IBufferStreamer* Connection)
{
	ITCP* TCP = (ITCP*) GetStaticClass("ICP");

	if ( TCP ) { TCP->CloseConnection(Connection); }
}















//=============================================================================
// Everything after this point is deprecated.
//=============================================================================







#if defined(Maxsi_Support_Linux)

#ifdef Maxsi_Support_Epoll
MaxsiAsyncEpoll
#else
//#error "There is no async IO handler installed on this system!"
#endif
MyAsync;

MaxsiAsync* Async()
{
	return &MyAsync;
}


MaxsiAsync::MaxsiAsync() { }
MaxsiAsync::~MaxsiAsync() { }

#ifdef Maxsi_Support_Epoll

// TODO: The epoll interface, when used with the EPOLLET flag ( Edge Triggered ) should use non-blocking file descriptors to avoid having a blocking read or write starve the task that is handling multiple file descriptors.

// TODO: Okay. When we get an event, set the flag that we can do IO, then do IO until we fail, and then set the flag that we can't, and then listen for events again.

MaxsiAsyncEpoll::MaxsiAsyncEpoll()
{
	EpollFD		=	epoll_create(Maxsi_Epoll_DefaultSize); // Returns -1 on failure.
}

MaxsiAsyncEpoll::~MaxsiAsyncEpoll()
{
	if ( EpollFD != -1 ) { close(EpollFD); }	
}

MaxsiHandle MaxsiAsyncEpoll::AsyncListen(short Port)
{
	int				Socket				=	-1;
	sockaddr_in		ServerAddress;

	// Create a handle.
	MaxsiHandle		Result		=	(MaxsiHandle)new MaxsiAsyncEpollIOHandle;

	// Check if the handle was created.
	bool			Success		=	(Result != NULL);
	
	if ( Success )
	{
		memset(Result, 0, sizeof(MaxsiAsyncEpollIOHandle));
		gettimeofday(&(((MaxsiAsyncEpollIOHandle*)Result)->Begun), NULL);
		gettimeofday(&(((MaxsiAsyncEpollIOHandle*)Result)->Last), NULL);

		((MaxsiAsyncEpollIOHandle*)Result)->State	=	MAXSI_EPOLL_IS_SOCKET_LISTENER;

		// Create a socket.
		Socket		=	socket(AF_INET, SOCK_STREAM, 0);

		// Check for error conditions.
		if ( Socket == -1 ) { Success = false; }
	}

	if ( Success )
	{
		memset(&ServerAddress, 0, sizeof(ServerAddress));

		ServerAddress.sin_family = AF_INET;
		ServerAddress.sin_addr.s_addr = INADDR_ANY;
		ServerAddress.sin_port = htons(Port);

		if ( bind(Socket, (struct sockaddr*)&ServerAddress, sizeof(ServerAddress)) < 0 )
		{
			PrintOutput("Could not bind socket!\n");
			Success = false;
		}
	}

	if ( Success )
	{
		int flags;
		// Set the socket as non-blocking.
		if ( (flags = fcntl(Socket, F_GETFL, 0)) < 0 || fcntl(Socket, F_SETFL, flags | O_NONBLOCK) < 0 )
		{
			PrintOutput("Could not set socket as non-blocking.\n");
			Success	= false;
		}
	}

	if ( Success )
	{
		listen(Socket, 5); MAXSI_TODO("Find better hardcoded constant!");
	}

	if ( Success )
	{
		((MaxsiAsyncEpollIOHandle*)Result)->FD	=	Socket;
	}

	if ( Success )
	{
		if ( SetUpAsyncIOHandleCallbacks(Result) != MAXSI_ERROR_SUCCESS ) { Success = false; }
	}

	if ( Success )
	{
		PrintOutput("Successfully listening at port %u\n", (unsigned int)Port);
	}
	else
	{
		PrintOutput("Could not listen at port %u\n", (unsigned int)Port);
	}

	if ( Success == false ) { if ( Socket != -1 ) { close(Socket); } }
	if ( Success == false ) { delete (MaxsiAsyncEpollIOHandle*)Result; return NULL; }

	return Result;	
}

MaxsiHandle MaxsiAsyncEpoll::AsyncConnect(MESTR* HostName, short Port)
{
	char*			HostNameSL			=	NULL;
	int				Socket				=	-1;
	hostent*		ServerAddressCopy	=	NULL;
	sockaddr_in		ServerAddress;

	// Create a handle.
	MaxsiHandle		Result		=	(MaxsiHandle)new MaxsiAsyncEpollIOHandle;

	// Check if the handle was created.
	bool			Success		=	(Result != NULL);
	
	if ( Success )
	{
		memset(Result, 0, sizeof(MaxsiAsyncEpollIOHandle));
		gettimeofday(&(((MaxsiAsyncEpollIOHandle*)Result)->Begun), NULL);
		gettimeofday(&(((MaxsiAsyncEpollIOHandle*)Result)->Last), NULL);

		// Convert the Host Name to the system locale.
		HostNameSL	=	MESTR2CHAR(HostName);

		// Check for error conditions.
		if ( HostNameSL == NULL ) { Success = false; }
	}

	if ( Success )
	{
		// Create a socket.
		Socket		=	socket(AF_INET, SOCK_STREAM, 0);

		// Check for error conditions.
		if ( Socket == -1 ) { Success = false; }
	}

	if ( Success )
	{
		int flags;
		// Set the socket as non-blocking.
		if ( (flags = fcntl(Socket, F_GETFL, 0)) < 0 || fcntl(Socket, F_SETFL, flags | O_NONBLOCK) < 0 )
		{
			PrintOutput("Could not set socket as non-blocking.\n");
			Success	= false;
		}
	}

	if ( Success )
	{
		// Do the DNS Lookup.
		ServerAddressCopy	=	gethostbyname(HostNameSL);
		MAXSI_TODO("gethostbyname is blocking!");
		MAXSI_TODO("The Open Group doesn't require gethostbyname to be thread safe!");

		// Check for error conditions.
		if ( ServerAddressCopy == NULL || ServerAddressCopy->h_addr == NULL ) { Success = false; }
	}

	if ( Success )
	{	
		// Reset the Server Adress structure.
		memset(&ServerAddress, 0, sizeof(ServerAddress));

		// Set the address type.
		ServerAddress.sin_family = AF_INET;

		// Copy the server address.
		memcpy(&ServerAddress.sin_addr.s_addr, ServerAddressCopy->h_addr, ServerAddressCopy->h_length);

		// Convert the remote port to network order (endianness).
		ServerAddress.sin_port = htons(Port);

		// Connect to the remote host and check for error conditions.	
		if ( connect(Socket, (sockaddr*)&ServerAddress, sizeof(ServerAddress)) != 0 && errno != EINPROGRESS ) { PrintOutput("'connect' failed with %i!\n", errno); Success = false; }
	}

	if ( Success )
	{
		((MaxsiAsyncEpollIOHandle*)Result)->FD	=	Socket;
	}

	if ( Success )
	{
		if ( SetUpAsyncIOHandleCallbacks(Result) != MAXSI_ERROR_SUCCESS ) { Success = false; }
	}

	if ( Success )
	{
		PrintOutput("Successfully connected to remote!\n");
	}
	else
	{
		PrintOutput("Could not connect to remote!\n");
	}

	IFDEL(HostNameSL);

	if ( Success == false ) { if ( Socket != -1 ) { close(Socket); } }
	if ( Success == false ) { delete (MaxsiAsyncEpollIOHandle*)Result; return NULL; }

	return Result;
}

MaxsiHandle MaxsiAsyncEpoll::AsyncOpenFile(MESTR* Path, FLAGS32 Mode)
{
	PrintOutput("Begun opening file!\n");

	// Convert the Path to the system locale!
	char*	PathSL	=	MESTR2CHAR(Path);

	// Check for error conditions.
	if ( PathSL == NULL ) { PrintOutput("Could not convert MESTR to char!\n"); return NULL; }	

	// Allocate handlefull of our favorite kind of local AsyncIOHandles!
	MaxsiAsyncEpollIOHandle*	Handle	=	new MaxsiAsyncEpollIOHandle;

	// Check for error conditions.
	if ( Handle == NULL ) { PrintOutput("Could not allocate a handle structure!\n"); delete[] PathSL; return NULL; }

	int		Flags	=	O_LARGEFILE | O_NONBLOCK;


	if ( (Mode & MAXSI_FILE_READ) && !(Mode & MAXSI_FILE_WRITE) ) { Flags |= O_RDONLY; }
	if ( !(Mode & MAXSI_FILE_READ) && (Mode & MAXSI_FILE_WRITE) ) { Flags |= O_WRONLY; }
	if ( (Mode & MAXSI_FILE_READ) && (Mode & MAXSI_FILE_WRITE) ) { Flags |= O_RDWR; }
	if ( Mode & MAXSI_FILE_CREATE ) { Flags = O_CREAT; }
	if ( Mode & MAXSI_FILE_TRUNCATE ) { Flags = O_TRUNC; }
	// Not available under Linux: if ( Mode & MAXSI_FILE_SEQUENTIAL ) { Flags |= FILE_FLAG_SEQUENTIAL_SCAN; }

	MAXSI_TODO("The open call is actually not async!");

	Handle->FD		=	open(PathSL, Flags);

	delete[] PathSL;

	if ( Handle->FD == -1 ) { PrintOutput("Could not open file with errono=%i!\n", errno); delete Handle; return NULL; }

	if ( SetUpAsyncIOHandleCallbacks((MaxsiHandle)Handle) != MAXSI_ERROR_SUCCESS ) { PrintOutput("Could not set up async callbacks on file '%s'!\n", PathSL); close(Handle->FD); delete Handle; return NULL; }

	PrintOutput("Correctly opened file.\n");

	return (MaxsiHandle)Handle;
}

MaxsiError MaxsiAsyncEpoll::SetUpAsyncIOHandleCallbacks(MaxsiHandle IOHandle)
{
	if ( EpollFD == -1 ) { MAXSI_TODO("Find a better error code."); return MAXSI_ERROR_UNSPECIFIED; }

	epoll_event Event;

	MaxsiAsyncEpollIOHandle*	Handle	=	(MaxsiAsyncEpollIOHandle*)IOHandle;

	memset(&Event, 0, sizeof(Event));

	Event.data.fd	=	Handle->FD;
	Event.data.ptr	=	(void*)Handle;
	Event.events	=	EPOLLERR | EPOLLET | EPOLLIN | EPOLLOUT | EPOLLHUP;

	int	Result = epoll_ctl(EpollFD, EPOLL_CTL_ADD, Handle->FD, &Event);

	if ( Result != 0 ) { PrintOutput("epoll_ctl failed with errno=%i on FD=%i\n", errno, Handle->FD); MAXSI_TODO("Find a better error code."); return MAXSI_ERROR_UNSPECIFIED; }

	return MAXSI_ERROR_SUCCESS;
}

#define Maxsi_Epoll_Concurrent_Event_Listens 64

MaxsiError MaxsiAsyncEpoll::MainLoop()
{
	PrintOutput("Entering mainloop.\n");

	size_t			EventsSize	=	Maxsi_Epoll_Concurrent_Event_Listens;
	epoll_event*	Events		=	new epoll_event[EventsSize];

#if 0
	BYTE			Buffer[4096];
	size_t			Size		=	4096;
	size_t			TotalRead	=	0;

	char*			WriteBuffer	=	"GET /Altered%20Transmission/Altered%20Transmission%20-%20Demo%209.zip HTTP/1.1\r\nHost: www.maxsi.dk\r\n\r\n";
	size_t			WriteSize	=	sizeof(char)*strlen(WriteBuffer);
	size_t			WriteLeft	=	WriteSize;
#endif

	timeval	LoopBegun;
	timeval	LoopLast;
	timeval	LoopNow;

	gettimeofday(&LoopBegun, NULL);
	gettimeofday(&LoopLast, NULL);
	gettimeofday(&LoopNow, NULL);

	FileSize_t		SumWritten	=	0;

	if ( Events == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	while ( true )
	{
		int	NumEvents	=	epoll_wait(EpollFD, Events, EventsSize, -1);

		if ( NumEvents == -1 ) { PrintOutput("Error in epoll_wait!\n"); MAXSI_TODO("Find a better error code."); return MAXSI_ERROR_UNSPECIFIED; }
	
		if ( NumEvents == 0 ) { PrintOutput("Help! I'm stuck in a busy wait!\n"); }

		for ( int I = 0; I < NumEvents; I++ )
		{
			MaxsiAsyncEpollIOHandle*	Handle	=	(MaxsiAsyncEpollIOHandle*)(Events[I].data.ptr);
			
			gettimeofday(&(Handle->Now), NULL);

			Handle->NowMS = (Handle->Now.tv_sec)*1000 + (Handle->Now.tv_usec/1000);
			Handle->LastMS = (Handle->Last.tv_sec)*1000 + (Handle->Last.tv_usec/1000);
			Handle->BegunMS = (Handle->Begun.tv_sec)*1000 + (Handle->Begun.tv_usec/1000);
			Handle->Elapsed = (float)(Handle->NowMS - Handle->BegunMS)/1000.f;

			Handle->PrintDebug = ( Handle->NowMS > Handle->LastMS + 1000 && (Handle->Elapsed > 0.1f) );

			if ( Handle->PrintDebug ) { PrintOutput("\r"); }

			FileSize_t	WrittenDiff		=	Handle->TotalWritten;

			if ( Handle->State & MAXSI_EPOLL_IS_SOCKET_LISTENER )
			{
				/*while ( */AcceptClient(Handle) /*== MAXSI_ERROR_SUCCESS ) { }*/;
			}
			else
			{	
				if ( Events[I].events & EPOLLERR ) { PrintOutput("An error occured on FD=%i.\n", Handle->FD); }
				if ( Events[I].events & EPOLLHUP ) { PrintOutput("A hangup occured on FD=%i.\n", Handle->FD); }
				if ( Events[I].events & EPOLLIN ) { DoRead(Handle); }
				if ( Events[I].events & EPOLLOUT ) { DoWrite(Handle); }
			}

			SumWritten		+=	Handle->TotalWritten - WrittenDiff;

			gettimeofday(&LoopNow, NULL);

			if ( true && Handle->State & MAXSI_EPOLL_IS_SERVER && LoopNow.tv_sec > LoopLast.tv_sec )
			{
				PrintOutput("Server performance: %.2f MiB/s\n", ((float)SumWritten)/1024.f/1024.f);
				LoopLast	=	LoopNow;
				SumWritten	=	0;
			}

			if ( false && Handle->PrintDebug && Handle->State & MAXSI_EPOLL_IS_SERVER && Handle->Elapsed )
			{
				float 	WrittenMiB	=	SumWritten/1024.f/1024.f;
				float	Speed		=	SumWritten / Handle->Elapsed;
				float	SpeedMiB	=	Speed/1024.f/1024.f;
				float	SpeedMbits	=	SpeedMiB*8.0f;

				PrintOutput("\rWritten %.0f MiB %.0f MiB/s (%.0f Mbits/s) over %lu s", WrittenMiB, SpeedMiB, SpeedMbits, (Handle->NowMS - Handle->BegunMS)/1000);
			} 

			//if ( Handle->PrintDebug && Handle->State & MAXSI_EPOLL_STATE_READ ) { PrintOutput("R "); }
			//if ( Handle->PrintDebug && Handle->State & MAXSI_EPOLL_STATE_WRITE ) { PrintOutput("W "); }

			//if ( Handle->PrintDebug ) { PrintOutput(".      "); gettimeofday(&Handle->Last, NULL); }
			//if ( Handle->PrintDebug ) { v("\n"); gettimeofday(&Handle->Last, NULL); }
		}
	}

	PrintOutput("Left mainloop!\n");

	delete[] Events;	

	return MAXSI_ERROR_SUCCESS;
}


MaxsiError MaxsiAsyncEpoll::DoRead(MaxsiAsyncEpollIOHandle* Handle)
{
	size_t	LocalRead	=	Handle->TotalRead;

	while ( true )
	{
		ssize_t Read		=	read(Handle->FD, Handle->InBuf, MAXSI_EPOLL_BUF_SIZE);
	
		if ( Read == -1 && errno == EAGAIN ) { break; }
		{
			if ( Read == -1 || Read > SSIZE_MAX ) { PrintOutput("There was an error condition on FD=%i during reading!\n", Handle->FD); }

			if ( Read < 0 ) { Read = 0; } MAXSI_TODO("Test if this is correct behavior!");

			Handle->TotalRead	+=	Read;
		}
	}

	LocalRead	=	Handle->TotalRead - LocalRead;

	if ( LocalRead > 0 )
	{
		if ( Handle->PrintDebug )
		{
			float 	ReadMiB		=	Handle->TotalRead/1024.f/1024.f;
			float	Speed		=	Handle->TotalRead / Handle->Elapsed;
			float	SpeedMiB	=	Speed/1024.f/1024.f;
			float	SpeedMbits	=	SpeedMiB*8.0f;

			PrintOutput("Read FD=%i %.3f MiB (%.3f MiB/s) (%.3f Mbits/s) ", Handle->FD, ReadMiB, SpeedMiB, SpeedMbits);
			//PrintOutput("Read %zu (%.0f MiB) %.1f MiB/s (%.1f Mbits/s) ", LocalRead, ReadMiB, SpeedMiB, SpeedMbits);
		}
	}

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiAsyncEpoll::DoWrite(MaxsiAsyncEpollIOHandle* Handle)
{
	if ( Handle->State & MAXSI_EPOLL_IS_SERVER )
	{
		while ( true )
		{
			ssize_t Written	=	write(Handle->FD, Handle->OutBuf, MAXSI_EPOLL_BUF_SIZE);
			
			if ( Written == -1 && errno == EAGAIN ) { break; }

			if ( Written == -1 || Written > SSIZE_MAX ) { PrintOutput("There was an error condition on the IO Handle during reading!\n"); }

			if ( Written < 0 ) { Written = 0; } MAXSI_TODO("Test if this is correct behavior!");

			if ( Written > 0 ) { Handle->TotalWritten += Written; }
		}
	}
	else
	{
		//if ( Handle->PrintDebug ) { PrintOutput("Not Server: Wrote 1 B! "); }
		//write(Handle->FD, Handle->OutBuf, 1);
		Handle->State |= MAXSI_EPOLL_STATE_WRITE;
	}

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError MaxsiAsyncEpoll::AcceptClient(MaxsiAsyncEpollIOHandle* Handle)
{
	PrintOutput("Incoming connection at listener FD=%i.\n", Handle->FD);

	// Allocate handlefull of our favorite kind of local AsyncIOHandles!
	MaxsiAsyncEpollIOHandle*	NewHandle	=	new MaxsiAsyncEpollIOHandle;

	if ( NewHandle == NULL ) { return MAXSI_ERROR_MEM_ALLOC_FAIL; }

	NewHandle->TotalRead = 0;
	NewHandle->TotalWritten = 0;
	NewHandle->PrintDebug = false;
	NewHandle->NowMS = 0;
	NewHandle->LastMS = 0;
	NewHandle->BegunMS = 0;
	NewHandle->Elapsed = 0.0f;

	gettimeofday(&(NewHandle->Begun), NULL);
	gettimeofday(&(NewHandle->Now), NULL);
	gettimeofday(&(NewHandle->Last), NULL);

#if 0
	for ( size_t I = 0; I < MAXSI_EPOLL_BUF_SIZE; I++ )
	{
		NewHandle->OutBuf[I]	=	rand()*255/RAND_MAX;
	}
#endif

	NewHandle->State			=	MAXSI_EPOLL_IS_SERVER;

	socklen_t	AddrLen	=	0;
	sockaddr	Addr;

	NewHandle->FD				=	accept(Handle->FD, (sockaddr*)&Addr, &AddrLen);

	PrintOutput("\rI has accepted client FD=%i\n", NewHandle->FD);

	if ( NewHandle->FD < 0 ) { delete NewHandle; return MAXSI_ERROR_UNSPECIFIED; }

	int flags;

	if ( (flags = fcntl(NewHandle->FD, F_GETFL, 0)) < 0 || fcntl(NewHandle->FD, F_SETFL, flags | O_NONBLOCK) < 0 )
	{
		PrintOutput("Could not set client %i as non-blocking!\n", NewHandle->FD);
		MAXSI_TODO("Check return value."); 
	}

	if ( SetUpAsyncIOHandleCallbacks((MaxsiHandle)NewHandle) != MAXSI_ERROR_SUCCESS )
	{
		PrintOutput("Could not set callbacks for client %i!\n", NewHandle->FD);
		MAXSI_TODO("Check return value.");	
	}

	listen(Handle->FD, 5);

	return MAXSI_ERROR_SUCCESS;
}

#endif

#endif

EndMaxsiNamespace

